/*
    采用模板方法模式，自定义封装一个socket模板
    将共有的方法设置为虚函数放在基类，将固定的方法步骤实现为对外的接口
    根据开发者的需求，自行继承，实现虚方法
*/

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <memory>

namespace socket_ns
{
    class TcpSocket;

    class Socket 
    {
    private:
        virtual void CreateSocket() = 0;
        virtual void Bind(struct sockaddr *loacladdr) = 0;
        virtual void Listen() = 0;
        virtual void Connect(struct sockaddr *destaddr) = 0;

    public:
        //virtual shared_ptr<TcpSocket> Accept(struct sockaddr_in *srcaddr) = 0;
        virtual int Accept(struct sockaddr_in *srcaddr, int& code) = 0;
        virtual void Recv(string& packstr) = 0;
        virtual void Send(string& packstr) = 0;
        virtual int getsockfd() = 0;
        virtual ~Socket() = 0;
        // 这里有个坑
        // 如果析构函数设置为纯虚函数之后，在使用多态时，通过父类指针或引用销毁对象时，会报纯虚析构函数没有定义（没有函数体）
        // 因为
        // 1、析构函数和构造函数与其它函数不同，因为他们会产生链式调用，当释放子类对象的时候，
        // 子类析构会向上链式调用父类析构，当调用到纯虚函数的时候，纯虚函数没有函数体，所有报未定义的错误
        // 2、纯虚函数可以有函数体，只是我们通常不会调用纯虚函数，所有没有必要写而已，这里纯虚析构要特殊处理，要在类外定义函数，见下面

        void buildServerSocket(struct sockaddr *localaddr)
        {
            CreateSocket();
            Bind(localaddr);
            Listen();
        }

        void buildClientSocket(struct sockaddr *destaddr)
        {
            CreateSocket();
            Connect(destaddr);
        }
    };
    Socket::~Socket()
    {}

    class TcpSocket : public Socket
    {
    private:
        void CreateSocket() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd == -1)
            {
                LOG(FATAL, "socket fail to create!\n");
                exit(1);
            }
        }

        void Bind(struct sockaddr *loacladdr) override
        {
            if (::bind(_sockfd, loacladdr, sizeof(*loacladdr)) == -1)
            {
                LOG(FATAL, "bind is failed!\n");
                exit(1);
            }
        }

        void Listen() override
        {
            if (listen(_sockfd, 16) == -1)
            {
                LOG(FATAL, "listen is failed!\n");
                exit(1);
            }
        }

        void Connect(struct sockaddr *destaddr) override
        {
            if (connect(_sockfd, destaddr, sizeof(*destaddr)) == -1)
            {
                LOG(FATAL, "connect is failed!\n");
                exit(1);
            }
        }

    public:
        TcpSocket(int sockfd = -1) : _sockfd(sockfd)
        {
        }

        int Accept(struct sockaddr_in *srcaddr, int& code) override // code将错误码带出，上层需要根据错误码判断是底层断开了链接，还是事件处理完了
        {
            socklen_t len = sizeof(*srcaddr);
            int ret = accept(_sockfd, (struct sockaddr *)&srcaddr, &len);
            code = errno;
            return ret;
        }

        void Recv(string& packstr) override
        {
            char buf[1024] = {0};
            int n = recv(_sockfd, buf, sizeof(buf), 0);
            if(n < 0)
            {
                LOG(WARNING, "recv character is zero!\n");
                return;
            }
            buf[n] = 0;
            packstr += string(buf);
        }

        void Send(string& packstr) override
        {
            int n = send(_sockfd, packstr.c_str(), packstr.size(), 0);
            if(n < 0)
            {
                LOG(WARNING, "send character is zero!\n");
            }
        }

        int getsockfd() override
        {
            return _sockfd;
        }

        ~TcpSocket() override
        {
            if(_sockfd)
                close(_sockfd);
        }

    private:
        int _sockfd;
        // 根据场景，即可以当监听套接字、也可以当通信套接字（使用listen函数时，是监听套接字， 由socket函数赋值，
        // 使用recv、send函数，是通信套接字，由accept函数赋值）
    };
}
